package tripmaker

import eventos._
import Moneda.USD
import movimientos._
import org.joda.time.LocalDate
import Viaje._
import Ordering.Implicits._
import tripmaker.output.HTML

object Viaje {

  def costoLocal(eventos: Seq[Evento], pais: Pais) = {
    val monedaLocal = pais.moneda
    val loc = eventos.filter {
      case _: Comer => true
      case _: Dormir => true
      case _: ConseguirHospedaje => true
      case e => e.costo.moneda == monedaLocal
    }

    Monto(loc map { _.costo.in(monedaLocal).valor } sum, monedaLocal)
  }

  def costoExtranjero(eventos: Seq[Evento], paises: Pais*) = {
    val monedas = paises map { _.moneda }
    val loc = eventos.filter {
      case _: Comer => false
      case _: Dormir => false
      case _: ConseguirHospedaje => false
      case e => !monedas.contains(e.costo.moneda) 
    }

    Monto(loc map { _.costo.inUSD.valor } sum, USD)
  }
  
  def eventosConTerritorios(eventos: Seq[Evento], origen: Territorio): Seq[(Territorio, Evento)] = {
    var terrRef = origen
    for (e <- eventos) yield {
      e.territorio foreach { t => terrRef = t }

      e.territorio.getOrElse(terrRef) -> e
    }
  }
  
  case class Resumen(pais: Pais, eventos: Seq[Evento]) {
    def costo = Monto(eventos map { _.costo.inUSD.valor } sum, USD)
    def costoLocal = Viaje.costoLocal(eventos, pais)

    def costoExtranjero = Viaje.costoExtranjero(eventos, pais)

    override def toString = {
      "Resumen " + pais + "\n" +
        "costo: " + costo + "\n" +
        "costo moneda local: " + costoLocal + " (" + costoLocal.in(USD) + ") \n" +
        "costo moneda extranjera: " + costoExtranjero + "\n"
    }
  }
}



trait Viaje {
  implicit def intToOptionInt(i: Int): Option[Int] = Some(i)
  implicit val localDateOrdering: Ordering[LocalDate] = new Ordering[LocalDate] {
    def compare(x: LocalDate, y: LocalDate) = x.compareTo(y)
  }

  def nombre: String
  def logo: String
  def origen: Territorio

  def dias: Seq[Día]
  
  lazy val diaInicial: LocalDate = {
        val ahora = new LocalDate()

    val añoRef = dias.flatMap { _.año }.headOption
    val mesRef = dias.flatMap { _.mes }.headOption
    val díaRef = dias.flatMap { _.día }.headOption

    new LocalDate(
      añoRef getOrElse ahora.getYear,
      mesRef getOrElse ahora.getMonthOfYear,
      díaRef getOrElse ahora.getDayOfMonth)
  }

  def fecha(año: Option[Int] = None, mes: Option[Int] = None, día: Option[Int] = None)(eventos: Evento*)(actividades: Actividad*): Día =
    Día(año, mes, día, eventos, actividades)
  def fechab(año: Option[Int] = None, mes: Option[Int] = None, día: Option[Int] = None)(eventos: Evento*) = fecha(año, mes, día)(eventos: _*)()
  def fechan(año: Option[Int] = None, mes: Option[Int] = None, día: Option[Int] = None)(implicit costoComer: Monto with Comer.MontoComer, costoDormir: Monto with Dormir.MontoDormir) = fecha(año, mes, día)(Comer(),Comer(),Comer(),Dormir())()

  lazy val diasCompletos: Seq[Día] = {
    var ref = diaInicial

    val conDías = for (d <- dias) yield {
      d.año foreach { a => ref = ref.year.setCopy(a) }
      d.mes foreach { m => ref = ref.monthOfYear.setCopy(m) }
      d.día foreach { d => ref = ref.dayOfMonth.setCopy(d) }

      val act = ref
      ref = ref.plusDays(1)

      d.copy(año = act.getYear, mes = act.getMonthOfYear, día = act.getDayOfMonth)
    }

    var terrRef = conDías.flatMap { _.territorios }.headOption.getOrElse(origen)

    val conTerritorios = conDías map { d =>
      val evt = d.territorios.lastOption match {
        case Some(t) =>
          terrRef = t
          d.eventos
        case None =>
          MarcaTerritorio(terrRef) +: d.eventos
      }

      d.copy(eventos = evt)
    }

    conTerritorios
  }

  def eventosConTerritorios: Seq[(Territorio, Evento)] = 
    Viaje.eventosConTerritorios(diasCompletos flatMap { _.eventos }, origen)

  lazy val paises: Seq[Pais] = (origen.país +: dias.flatMap { _.eventos }.flatMap { _.territorio.map { _.país } }).distinct
  
  lazy val territorios: Seq[Territorio] = (origen +: dias.flatMap { _.eventos }.flatMap { _.territorio }).distinct

  lazy val costoTotal: Monto = Monto(diasCompletos.map { _.costoUSD.valor }.sum, USD)

  def total(pais: Pais) = Resumen(pais, eventosConTerritorios.filter { _._1.país == pais }.unzip._2)

  def costoFuturo(dia: Día) = Monto(diasCompletos dropWhile { d =>
    (for (f1 <- d.fecha; f2 <- dia.fecha) yield f1 <= f2).getOrElse(true)
  } map { _.costoUSD.valor } sum, USD)
  
  
  def main(args: Array[String]) {
	  println("Costo total: " + costoTotal)
	  println(paises map total mkString "\n")
	  
	  import HTML._
	  write(this)
	  write(wrap(this.nombre + " - lado A", paginasA(this)), this, "-a")
	  write(wrap(this.nombre + " - lado B", paginasB(this)), this, "-b")
	  write(portada(this), this, "-portada")
  }
}